/*
 *    Copyright 2009-2012 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.plugin;

import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.reflection.ExceptionUtil;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;

/**
 * @author Clinton Begin
 */

/**
 * 插件,用的代理模式
 */
public class Plugin implements InvocationHandler {

    private static final Log log = LogFactory.getLog(Plugin.class);

    //需要拦截的接口的实现类，如Executor
    private Object target;
    //拦截器的实现类
    private Interceptor interceptor;
    //key是clazz，value是需要拦截的名字，使用注解@Signature
    private Map<Class<?>, Set<Method>> signatureMap;

    private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
        this.target = target;
        this.interceptor = interceptor;
        this.signatureMap = signatureMap;
    }

    /**
     * @param target
     * @param interceptor
     * @return
     */
    public static Object wrap(Object target, Interceptor interceptor) {
        //取得签名Map
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        //取得要改变行为的类(ParameterHandler|ResultSetHandler|StatementHandler|Executor)
        Class<?> type = target.getClass();
        //取得接口,获取的是签名制定的接口
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        //产生代理。只有实现了ParameterHandler|ResultSetHandler|StatementHandler|Executor接口才产生代理，否则返回对象本身
        if (interfaces.length > 0) {
            log.error(target + " is the Signature clazz " + Arrays.toString(interfaces) + " , go to proxy");
            Object o = Proxy.newProxyInstance(
                    target.getClass().getClassLoader(),
                    interfaces,
                    //InvocationHandler
                    new Plugin(target, interceptor, signatureMap));
            log.error("      ----> proxy object = " + o);
            return o;
        }
        log.warn(target + " is not Signature clazz , return");
        return target;
    }

    /**
     * ParameterHandler|ResultSetHandler|StatementHandler|Executor 其中之一被代理。
     * 则代理的对象执行接口方法时，走这里
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // 看看如何拦截
            Set<Method> methods = signatureMap.get(method.getDeclaringClass());
            // 看哪些方法需要拦截
            if (methods != null && methods.contains(method)) {
                // 调用Interceptor.intercept，也即插入了我们自己的逻辑。 创建的对象是Invocation，其proceed方法是放行 当前方法
                return interceptor.intercept(new Invocation(target, method, args));
            }
            //最后还是执行原来逻辑
            return method.invoke(target, args);
        } catch (Exception e) {
            throw ExceptionUtil.unwrapThrowable(e);
        }
    }

    //取得签名Map，key是制定的接口名，value是method集合
    private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
        //取Intercepts注解，例子可参见ExamplePlugin.java
        Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
        // issue #251
        //必须得有Intercepts注解，没有报错
        if (interceptsAnnotation == null) {
            throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
        }
        //value是数组型，Signature的数组
        Signature[] sigs = interceptsAnnotation.value();
        //每个class里有多个Method需要被拦截,所以这么定义
        Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
        for (Signature sig : sigs) {
            Set<Method> methods = signatureMap.get(sig.type());
            if (methods == null) {
                methods = new HashSet<Method>();
                signatureMap.put(sig.type(), methods);
            }
            try {
                //通过注解获取要 增强的方法，放入到map
                Method method = sig.type().getMethod(sig.method(), sig.args());
                methods.add(method);
            } catch (NoSuchMethodException e) {
                throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
            }
        }
        return signatureMap;
    }

    //取得接口
    private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
        Set<Class<?>> interfaces = new HashSet<Class<?>>();
        while (type != null) {
            //判断当前接口，是否是签名的接口
            for (Class<?> c : type.getInterfaces()) {
                //貌似只能拦截ParameterHandler|ResultSetHandler|StatementHandler|Executor
                //拦截其他的无效
                //当然我们可以覆盖Plugin.wrap方法，达到拦截其他类的功能
                if (signatureMap.containsKey(c)) {
                    interfaces.add(c);
                }
            }
            type = type.getSuperclass();
        }
        return interfaces.toArray(new Class<?>[interfaces.size()]);
    }

}
